import {prop} from './prop';
import { grip, grap } from './list';

const createEmptyItem = () => {
  const item = {};
  Object.defineProperty(item, 'succ', {
    value: [],
    enumerable: false,
  })
  return item;
}

export function move(dest, from, {pushSide='HEAD', withPred=false}={}) {

  const {succ} = dest;
  
  const side = {HEAD:'unshift', TAIL:'push'}[pushSide];

  if (Array.isArray(from)) {
    for (let i = 0; i < from.length; i++) {
      succ[side](from[i]);

      if (withPred) {
        Object.defineProperty(from[i], 'pred', {
          value: dest,
        })
        from[i].pred = dest;
      }
    }
  } else {
    succ[side](from);

    if (withPred) {
      Object.defineProperty(from[i], 'pred', {
        value: dest,
      })
    }
  }
}

export function nest(from, {dest, propSpec='curr.code', delim="", ...moveSpecs}={}) {

  function arrayPrefix(array, patt) { 
    for (let i = 0; i < patt.length; i++) if (array[i] !== patt[i]) {
      return false;
    }
    return true;
  }
  
  dest ??= createEmptyItem();

  const genIdent = delim === ''
    ? (propVal) => propVal.length
    : (propVal) => propVal.split(delim).length;

  const geneFunc = (rec) => genIdent(prop(rec, propSpec));

  const gens = grap(grip(from, geneFunc));

  const matchFunc = delim === ''
    ? (prevVal, succVal) => succVal.startsWith(prevVal)
    : (prevVal, succVal) => arrayPrefix(succVal.split(delim), prevVal.split(delim))

  const seekFunc = (prev, succ) => {
    const prevVal = prop(prev, propSpec);
    const succVal = prop(succ, propSpec);

    if (matchFunc(prevVal, succVal)) {
      move(prev, succ, moveSpecs)
      return true;
    } else {
      return false;
    };
  }

  for (let gene = gens.length - 2; gene >= 0; gene --) {

    const preds = gens[gene];
    const currs = gens[gene+1];
    
    for (let curr = currs.length - 1; curr >= 0; curr --)
    for (let pred = preds.length - 1; pred >= 0; pred --)
      if (seekFunc(preds[pred], currs[curr])) 
        break;
  }
  
  for (const ancestors of gens[0]) {
    move(dest, ancestors, moveSpecs);
  }

  return dest;
}

export function trav ({succ}, action, {order='BY-ENTER'}={}){

  if (action === undefined) {
    throw Error('trav: action must be specified');
  }

  if (!['BY-ENTER', 'BY-LEAVE'].includes(order)){
    throw Error('trav: order must be either BY-ENTER or BY-LEAVE');
  }

  for (let index = 0; index < succ.length; index ++) {
    const subItem = succ[index];

    (order === 'BY-ENTER') && action(subItem, index);
      
    trav(subItem, action, {order});

    (order === 'BY-LEAVE') && action(subItem, index);
  }
}

export function flat({succ: list}) {

  const stack = list.slice();
  const unnested = [];

  while(stack.length > 0) {
    const next = stack.pop();
    stack.push(...next.succ);
    unnested.push(next);
  }

  return unnested;

}

export function find(item, propMap) {

  const result = [];

  const propList = Object.entries(propMap);

  trav(item, (rec) => {

    const isMatching = propList.every(([propSpec, patt]) => {
      return Array.isArray(patt)
      ? patt.includes(prop(rec, propSpec))
      : prop(rec, propSpec) === patt
    })

    if (isMatching) {
      result.push(rec);
    }
  })

  return result;
}
